package my.springboot.service.impl;

import com.baomidou.mybatisplus.core.conditions.Wrapper;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.github.wxpay.sdk.WXPayUtil;
import lombok.extern.slf4j.Slf4j;
import my.springboot.domain.primary.User;
import my.springboot.domain.primary.Withdrawal;
import my.springboot.exception.BaseException;
import my.springboot.mapper.UserMapper;
import my.springboot.mapper.WithdrawalMapper;
import my.springboot.po.bo.WithdrawalAdminBO;
import my.springboot.po.enums.WithdrawalApplyEnum;
import my.springboot.po.enums.WithdrawalAuditEnum;
import my.springboot.po.vo.ReturnCode;
import my.springboot.po.vo.WithdrawalAdminListVo;
import my.springboot.po.vo.WithdrawalAdminVo;
import my.springboot.po.vo.WithdrawalVo;
import my.springboot.service.GlobalLockService;
import my.springboot.service.MessagePushService;
import my.springboot.service.WechatPayService;
import my.springboot.service.WithdrawalService;
import my.springboot.util.DateUtils;
import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.time.DateFormatUtils;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.PlatformTransactionManager;
import org.springframework.transaction.TransactionDefinition;
import org.springframework.transaction.TransactionStatus;

import java.math.BigDecimal;
import java.math.RoundingMode;
import java.util.*;
import java.util.concurrent.TimeUnit;

import static my.springboot.common.Constant.*;

/**
 * <p>
 * 提现历史 服务实现类
 * </p>
 *
 * @author fengbo
 * @since 2019-08-23
 */
@Slf4j
@Service
public class WithdrawalServiceImpl extends ServiceImpl<WithdrawalMapper, Withdrawal> implements WithdrawalService {

    @Autowired
    private GlobalLockService globalLockService;
    @Autowired
    private WechatPayService wechatPayService;
    @Autowired
    private MessagePushService messagePushService;

    @Autowired
    private PlatformTransactionManager platformTransactionManager;
    @Autowired
    private TransactionDefinition transactionDefinition;

    @Autowired
    private UserMapper userMapper;
    @Autowired
    private WithdrawalMapper withdrawalMapper;

    @Override
    public void saveWithdrawal(BigDecimal amount, String userIp) {
        int uid = 11;
        if (uid <= 0) {
            throw new BaseException(ReturnCode.MISSING_TOKEN);
        }
        String key = String.format(PAY_USER_KEY, uid);
        String value = UUID.randomUUID().toString();
        try {
            if (globalLockService.lock(key, value, 30, TimeUnit.MINUTES)) {
                User user = userMapper.selectById(uid);
                Withdrawal withdrawal = new Withdrawal();
                // 设置账户当前余额
                BigDecimal userMoney = new BigDecimal(user.getAccountMoney());
                // 将提现值由元转换为分
                BigDecimal withdrawalAmount = amount.multiply(new BigDecimal(100));
                // 获取变动后余额
                int balance = userMoney.subtract(withdrawalAmount).intValueExact();
                withdrawal.setUserId(uid);
                withdrawal.setUserName(user.getWxNickName());
                // 设置变动后余额
                BigDecimal afterBalance = new BigDecimal(balance).divide(new BigDecimal(100), 2, RoundingMode.DOWN);
                withdrawal.setAccountBalance(afterBalance);
                withdrawal.setAuditStatus(WithdrawalAuditEnum.UN_REVIEWED.getCode());
                withdrawal.setApplyStatus(WithdrawalApplyEnum.UN_REVIEWED.getCode());
                withdrawal.setApplyAmount(amount);
                withdrawal.setRealAmount(amount);
                withdrawal.setWithdrawalCode(UUID.randomUUID().toString().replaceAll("-", "").toUpperCase());

                // 判断账号余额是否足够提现
                if (balance < 0) {
                    withdrawal.setAuditStatus(WithdrawalAuditEnum.REJECTED.getCode());
                    withdrawal.setRejectReason("余额不足，不能提现！");
                    log.warn("用户{}[id={}]余额不足，不能提现！订单号[{}]，账号余额[{}]元", user.getWxNickName(), user.getId(),
                            withdrawal.getWithdrawalCode(), withdrawal.getAccountBalance());
                    // save(withdrawal);
                    throw new BaseException(ReturnCode.INSUFFICIENT_BALANCE);
                }

                // 判断是否有待审核订单，如果有不能再次申请
                long count1 = count(new QueryWrapper<Withdrawal>().lambda().eq(Withdrawal::getUserId, uid)
                        .eq(Withdrawal::getAuditStatus, WithdrawalAuditEnum.UN_REVIEWED.getCode())
                );
                if (count1 > 0) {
                    withdrawal.setAuditStatus(WithdrawalAuditEnum.REJECTED.getCode());
                    withdrawal.setRejectReason("存在未审核提单，不能重复提现！");
                    log.warn("用户{}[id={}]存在未审核提单，不能重复提现！订单号[{}]", user.getWxNickName(), user.getId(), withdrawal.getWithdrawalCode());
                    throw new BaseException(ReturnCode.REPEAT_WITHDRAWAL);
                }

                // 每天提现超过三次，不能再次提现
                long count2 = count(new QueryWrapper<Withdrawal>().lambda().eq(Withdrawal::getUserId, uid)
                        .gt(Withdrawal::getCreateTime, DateUtils.getBeginTimeForDay()));
                if (count2 >= 3) {
                    withdrawal.setAuditStatus(WithdrawalAuditEnum.REJECTED.getCode());
                    withdrawal.setRejectReason("今天已提现3次，不能再次提现");
                    log.warn("用户{}[id={}]今天已提现3次，不能再次提现！订单号[{}]", user.getWxNickName(), user.getId(),
                            withdrawal.getWithdrawalCode());
                    throw new BaseException(ReturnCode.WITHDRAWAL_DAY_NUMBER_OUT);
                }

                // 每月提现超过五次，不能再次提现
                long count3 = count(new QueryWrapper<Withdrawal>().lambda().eq(Withdrawal::getUserId, uid)
                        .gt(Withdrawal::getCreateTime, DateUtils.getMonthOfFirstDay()));
                if (count3 >= 5) {
                    withdrawal.setAuditStatus(WithdrawalAuditEnum.REJECTED.getCode());
                    withdrawal.setRejectReason("本月已提现5次，不能再次提现");
                    log.warn("用户{}[id={}]本月已提现5次，不能再次提现！订单号[{}]", user.getWxNickName(), user.getId(), withdrawal.getWithdrawalCode());
                    throw new BaseException(ReturnCode.WITHDRAWAL_MOUTH_NUMBER_OUT);
                }

                // 发送提现通知消息
                messagePushService.sendWithdrawalMsg(user, withdrawal);

                TransactionStatus transactionStatus = null;
                try {
                    // 手动开启本地事务
                    transactionStatus = platformTransactionManager.getTransaction(transactionDefinition);
                    // 提前减余额
                    user.setAccountMoney(balance);
                    user.setWithdrawalMoney(withdrawalAmount.intValueExact() + user.getWithdrawalMoney());
                    userMapper.updateById(user);
                    save(withdrawal);
                    log.warn("用户{}[id={}]单号为[{}]的提现进行了减余额操作，减少之前余额[{}]，减少后余额[{}]", user.getWxNickName(), user.getId(),
                            withdrawal.getWithdrawalCode(), userMoney.intValueExact(), balance);
                    // 事务提交
                    platformTransactionManager.commit(transactionStatus);
                } catch (Exception e) {
                    // 事务回滚
                    if (transactionStatus != null) {
                        platformTransactionManager.rollback(transactionStatus);
                    }
                    log.error("用户[{}]id=[{}]提现申请失败，申请金额[{}],当前账户余额[{}]，ex=[{}]", user.getWxNickName(),
                            user.getId(), amount, withdrawal.getAccountBalance(), e.getMessage(), e);
                    throw new BaseException(ReturnCode.APPLY_ERROR);
                }
            } else {
                throw new BaseException(ReturnCode.REPEAT_WITHDRAWAL);
            }
        } finally {
            globalLockService.unlock(key, value);
        }
    }

    @Override
    public void updateWithdrawalById(WithdrawalVo withdrawalVo) {
        Withdrawal withdrawal = getById(withdrawalVo.getId());
        if (withdrawal == null || withdrawal.getUserId() == null || withdrawal.getUserId() <= 0) {
            log.error("订单异常，withdrawal=[{}]", withdrawal);
            throw new BaseException(ReturnCode.AUDIT_ERROR);
        }
        String key = String.format(PAY_USER_KEY, withdrawal.getUserId());
        String value = UUID.randomUUID().toString();
        try {
            if (globalLockService.lock(key, value, 30, TimeUnit.MINUTES)) {
                if (Boolean.TRUE.equals(withdrawalVo.getStatus())) {
                    // 审核同意
                    log.warn("用户[id={}]单号为[{}]的提现请求审核同意", withdrawal.getUserId(), withdrawal.getWithdrawalCode());
                    auditTransfer(withdrawal, withdrawalVo.getIpAddress());
                } else {
                    // 审核拒绝
                    withdrawal.setId(withdrawalVo.getId());
                    withdrawal.setAuditStatus(WithdrawalAuditEnum.REJECTED.getCode());
                    withdrawal.setRejectReason(withdrawalVo.getValue());
                    auditReject(withdrawal);
                }
            } else {
                throw new BaseException(ReturnCode.REPEAT_AUDIT);
            }
        } finally {
            globalLockService.unlock(key, value);
        }
    }

    /**
     * 审核转账逻辑
     */
    private void auditTransfer(Withdrawal withdrawal, String userIp) {
        if (withdrawal == null || StringUtils.isBlank(withdrawal.getWithdrawalCode())) {
            if (withdrawal == null) {
                log.error("订单不存在！！！");
            } else {
                log.error("用户[id={}]存在未审核提单，不能重复提现！订单号[{}]", withdrawal.getUserId(),
                        withdrawal.getWithdrawalCode());
            }
            throw new BaseException(ReturnCode.WITHDRAWAL_CODE_ERROR);
        }
        if (WithdrawalApplyEnum.UN_REVIEWED.getCode() != withdrawal.getApplyStatus()
                || WithdrawalAuditEnum.UN_REVIEWED.getCode() != withdrawal.getAuditStatus()) {
            log.error("用户[id={}]的订单[{}]状态异常，审核状态是[{}]，划款状态是[{}]", withdrawal.getUserId(),
                    withdrawal.getWithdrawalCode(), withdrawal.getAuditStatus(), withdrawal.getApplyStatus());
            throw new BaseException(ReturnCode.WITHDRAWAL_CODE_ERROR);
        }

        Integer uid = withdrawal.getUserId();
        User user = userMapper.selectById(uid);
        TransactionStatus transactionStatus = null;

        String key = String.format(PAY_ORDER_KEY, withdrawal.getWithdrawalCode());
        String value = UUID.randomUUID().toString();
        try {
            // 使用订单号作为锁
            if (globalLockService.lock(key, value, 30, TimeUnit.MINUTES)) {
                // 手动开启本地事务
                transactionStatus = platformTransactionManager.getTransaction(transactionDefinition);
                BigDecimal withdrawalMoney = new BigDecimal(user.getWithdrawalMoney());
                // 判断账号余额是否足够提现
                BigDecimal realAmount = withdrawal.getRealAmount().multiply(new BigDecimal(100));
                int balance = withdrawalMoney.subtract(realAmount).intValueExact();
                if (balance < 0) {
                    log.warn("用户{}[id={}]已减余额不足，提现失败！订单号[{}]，账号余额[{}]", user.getWxNickName(), user.getId(),
                            withdrawal.getWithdrawalCode(), withdrawalMoney);
                    throw new BaseException(ReturnCode.INSUFFICIENT_BALANCE);
                }
                // 转账成功后需要修改itaoke_user表中的余额
                user.setWithdrawalMoney(balance);
                userMapper.updateById(user);

                // 更新提现表
                withdrawal.setAuditStatus(WithdrawalAuditEnum.APPROVED.getCode());
                withdrawal.setApplyStatus(WithdrawalApplyEnum.APPROVED.getCode());
                Date now = new Date();
                withdrawal.setAccountTime(now);
                withdrawal.setUpdateTime(now);
                updateById(withdrawal);

                String wxRes = wechatPayService.payToChange(withdrawalMoney.intValueExact(), withdrawal.getWithdrawalCode(), user.getWxOpenId(), userIp);
                // 根据微信的返回结果判断是否成功
                Map<String, String> wxResMap = WXPayUtil.xmlToMap(wxRes);
                withdrawal.setRejectReason(wxResMap.get("return_msg"));
                if (!"SUCCESS".equals(wxResMap.get("result_code"))) {
                    log.error("用户{}[id={}]提现微信支付接口调用失败！订单号[{}], retMsg=[{}]", user.getWxNickName(), user.getId(),
                            withdrawal.getWithdrawalCode(), wxRes);
                    throw new BaseException(ReturnCode.WECHAT_PAY_ERROR);
                }
                log.warn("用户{}[id={}]的订单[{}]提现成功", user.getWxNickName(), user.getId(), withdrawal.getWithdrawalCode());
                // 事务提交
                platformTransactionManager.commit(transactionStatus);
            } else {
                throw new BaseException(ReturnCode.REPEAT_AUDIT);
            }
        } catch (Exception e) {
            // 事务回滚
            if (transactionStatus != null) {
                platformTransactionManager.rollback(transactionStatus);
            }
            log.error("订单[{}]提现失败！", withdrawal.getWithdrawalCode());
            withdrawal.setAuditStatus(WithdrawalAuditEnum.APPROVED.getCode());
            withdrawal.setApplyStatus(WithdrawalApplyEnum.FAILED.getCode());
            auditReject(withdrawal);
            if (e instanceof BaseException) {
                throw (BaseException) e;
            }
            throw new BaseException(ReturnCode.WECHAT_PAY_ERROR);
        } finally {
            globalLockService.unlock(key, value);
        }
        // 发送到账成功消息
        messagePushService.sendWxAccountMsg(user, withdrawal);
    }

    private void auditReject(Withdrawal withdrawal) {
        withdrawal.setUpdateTime(new Date());
        int withdrawalMoney = withdrawal.getRealAmount().multiply(new BigDecimal(100)).intValueExact();
        TransactionStatus transactionStatus = null;
        try {
            transactionStatus = platformTransactionManager.getTransaction(transactionDefinition);
            User user = userMapper.selectById(withdrawal.getUserId());
            updateById(withdrawal);
            user.setAccountMoney(user.getAccountMoney() + withdrawalMoney);
            user.setWithdrawalMoney(user.getWithdrawalMoney() - withdrawalMoney);
            userMapper.updateById(user);
            log.warn("用户{}[id={}]单号为[{}]的提现请求被拒绝，拒绝理由[{}]", user.getWxNickName(), user.getId(),
                    withdrawal.getWithdrawalCode(), withdrawal.getRejectReason());
            platformTransactionManager.commit(transactionStatus);
        } catch (Exception e) {
            if (transactionStatus != null) {
                platformTransactionManager.rollback(transactionStatus);
            }
        }
    }

    @Override
    public WithdrawalAdminVo getWithdrawalList(WithdrawalAdminBO withdrawalForm) {
        IPage<Withdrawal> page = new Page<>(1, 16);
        page(page, getQueryWrapper(withdrawalForm));
        BigDecimal totalAmount = withdrawalMapper.sumRealAmount(withdrawalForm);
        WithdrawalAdminVo withdrawalAdminVo = new WithdrawalAdminVo();
        withdrawalAdminVo.setList(withdrawalToVo(page.getRecords()));
        withdrawalAdminVo.setTotalApplyAmount(totalAmount);
        withdrawalAdminVo.setTotalRealAmount(totalAmount);
        return withdrawalAdminVo;
    }

    private List<WithdrawalAdminListVo> withdrawalToVo(List<Withdrawal> list) {
        List<WithdrawalAdminListVo> result = new ArrayList<>(list.size());
        for (Withdrawal withdrawal : list) {
            WithdrawalAdminListVo withdrawalAdminListVo = new WithdrawalAdminListVo();
            BeanUtils.copyProperties(withdrawal, withdrawalAdminListVo);
            withdrawalAdminListVo.setAuditStatus(WithdrawalAuditEnum.getMsgByCode(withdrawal.getAuditStatus()));
            withdrawalAdminListVo.setApplyStatus(WithdrawalApplyEnum.getMsgByCode(withdrawal.getApplyStatus()));
            if (withdrawal.getAccountTime() != null) {
                withdrawalAdminListVo.setAccountTime(DateFormatUtils.format(withdrawal.getAccountTime(), NORMAL_TIME_FORMAT));
            }
            if (withdrawal.getCreateTime() != null) {
                withdrawalAdminListVo.setCreateTime(DateFormatUtils.format(withdrawal.getCreateTime(), NORMAL_TIME_FORMAT));
            }
            result.add(withdrawalAdminListVo);
        }
        return result;
    }

    private Wrapper<Withdrawal> getQueryWrapper(WithdrawalAdminBO withdrawalForm) {

        return new QueryWrapper<Withdrawal>().lambda()
                .eq(StringUtils.isNotEmpty(withdrawalForm.getUserName()), Withdrawal::getUserName, withdrawalForm.getUserName())
                .eq(StringUtils.isNotEmpty(withdrawalForm.getWithdrawalCode()), Withdrawal::getWithdrawalCode, withdrawalForm.getWithdrawalCode())
                .gt(withdrawalForm.getBeginAmountDecimal() != null, Withdrawal::getRealAmount, withdrawalForm.getBeginAmountDecimal())
                .lt(withdrawalForm.getEndAmountDecimal() != null, Withdrawal::getRealAmount, withdrawalForm.getEndAmountDecimal())
                .between(StringUtils.isNotEmpty(withdrawalForm.getStartAccountTime()) && StringUtils.isNotEmpty(withdrawalForm.getEndAccountTime()),
                        Withdrawal::getAccountTime, withdrawalForm.getStartAccountTime(), withdrawalForm.getEndAccountTime())
                .between(StringUtils.isNotEmpty(withdrawalForm.getStartCreateTime()) && StringUtils.isNotEmpty(withdrawalForm.getEndCreateTime()),
                        Withdrawal::getCreateTime, withdrawalForm.getStartAccountTime(), withdrawalForm.getEndAccountTime())
                .eq(withdrawalForm.getApplyStatus() != null, Withdrawal::getApplyStatus, withdrawalForm.getApplyStatus())
                .eq(withdrawalForm.getAuditStatus() != null, Withdrawal::getAuditStatus, withdrawalForm.getAuditStatus())
                .orderByDesc(Withdrawal::getCreateTime);
    }
}
